using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Linq;
using System.Collections;
using System.Linq.Expressions;
using System.Reflection;

namespace YY.WCFLib
{
    /// <author>Yazeed Hamdan</author>
    /// http://www.codeproject.com/KB/linq/LINQToSQLBaseCRUDClass.aspx
    /// <summary>
    /// Handles most of DB operations from Insert, Update, Delete and Select using Linq To SQL
    /// </summary>
    /// <typeparam name="T">Type of class</typeparam>
    /// <typeparam name="DC"><see cref="System.Data.Linq.DataContext"/></typeparam>
    /// <example><code>
    /// //This example assumes that you already have created a table called Address and it has an AddressID column
    /// //which will be transformed by LINQ to SQL designer to ADDRESS class with ADDRESSID property
    /// //This is your Internal DB Process which will be called by the facade class.
    /// [C#]
    /// 
    ///    internal class AddressDataBaseProcess : DataBaseProcessBase<Address, My_DataDataContext>
    ///     {
    ///     public int Add(Address entity)
    ///     {
    ///      return Convert.ToInt32(base.Add(entity, "AddressID"));
    ///     }
    ///      public IList<Address> GetAllAddresses(int from, int to)
    ///    {           
    ///        return base.Get(null, null,from,to);
    ///    }
    ///    public Address GetAddressById(int addressId)
    ///    {
    ///        return base.Get(null, a => a.AddressID == addressId,0,1)[0];
    ///    }
    ///     public void UpdateAddress(Address address)
    ///    {
    ///        Update(memberAddress, a => a.AddressID == address.AddressID);
    ///    }
    ///    public void Delete(int addressId)
    ///    {
    ///        base.Delete(a => a.AddressID == addressId);
    ///    } 
    /// </code></example>
    internal class DataBaseProcessBase<T, DC>
        where T : class, new()
        where DC : DataContext, new()
    {
        /// <summary>
        /// Adds a new record to the DB
        /// </summary>
        /// <param name="entity">Current Object</param>
        /// <param name="IdPropertyName">Name of the property containing identity Column or the ID returned by 
        /// the DB</param>
        /// <returns><see cref="System.Object"/> </returns>
        protected virtual object Add(T entity, string IdPropertyName)
        {
            using (DC db = new DC())
            {
                db.GetTable<T>().InsertOnSubmit(entity);
                db.SubmitChanges();
            }

            return entity.GetType().GetProperty(IdPropertyName).GetValue(entity, null);
        }
        /// <summary>
        /// Select From DB on the defined query
        /// </summary>
        /// <param name="options"><see cref="System.Data.Linq.DataLoadOptions"/></param>
        /// <param name="query">Select Query</param>
        /// <param name="from">for pagination Purposes, starting Index</param>
        /// <param name="to">for pagination Purposes, End Index</param>
        /// <returns>collection of the current type, <see cref="System.System.Collections.Generic.IList<T>"/></returns>
        /// <remarks>if "to" parameter was passed as 0, it will be defaulted to 100, you can replace it by
        /// a valued defined in the config, and another point of interest, if from > to, from will be
        /// reseted to 0.
        /// 
        /// if there is no query defined, all results will be returned, and also if there is no load data options
        /// defined, the results will contain only the entity specified with no nested data (objects) within that entity.
        /// </remarks>
        protected virtual IList<T> Get(DataLoadOptions options, Expression<Func<T, bool>> query, int from, int to)
        {
            IList<T> list = null;

            if (to == 0)
                to = 100;

            if (from > to)
                from = 0;

            using (DC db = new DC())
            {
                if (null != options)
                    db.LoadOptions = options;

                if (null == query)
                    list = db.GetTable<T>().Skip(from).Take(to - from).ToList();
                else
                    list = db.GetTable<T>().Where(query).Skip(from).Take(to - from).ToList();
            }

            return list;
        }
        /// <summary>
        /// Select From DB on the defined query
        /// </summary>        
        /// <param name="query">Select Query</param>
        /// <param name="from">for pagination Purposes, starting Index</param>
        /// <param name="to">for pagination Purposes, End Index</param>
        /// <returns>collection of the current type, <see cref="System.System.Collections.Generic.IList<T>"/></returns>
        /// <remarks>if "to" parameter was passed as 0, it will be defaulted to 100, you can replace it by
        /// a valued defined in the config, and another point of interest, if from > to, from will be
        /// reseted to 0</remarks>
        protected virtual IList<T> Get(Expression<Func<T, bool>> query, int from, int to)
        {
            return Get(null, query, from, to);
        }
        /// <summary>
        /// Select All From DB
        /// </summary>        
        /// <param name="from">for pagination Purposes, starting Index</param>
        /// <param name="to">for pagination Purposes, End Index</param>
        /// <returns>collection of the current type, <see cref="System.System.Collections.Generic.IList<T>"/></returns>
        /// <remarks>if "to" parameter was passed as 0, it will be defaulted to 100, you can replace it by
        /// a valued defined in the config, and another point of interest, if from > to, from will be
        /// reseted to 0</remarks>
        protected virtual IList<T> Get(int from, int to)
        {
            return Get(null, null, from, to);
        }
        /// <summary>
        /// Deletes the entity upon the defined query
        /// </summary>
        /// <param name="query">Delete Query</param>
        protected virtual void Delete(Expression<Func<T, bool>> query)
        {
            using (DC db = new DC())
            {
                db.GetTable<T>().DeleteOnSubmit(db.GetTable<T>().Where(query).Single());
                db.SubmitChanges();
            }
        }
        /// <summary>
        /// Updates Entity
        /// </summary>
        /// <param name="entity">Entity which hold the updated information</param>
        /// <param name="query">query to get the same entity from db and perform the update operation</param>
        /// <remarks>this method will do dynamic property mapping between the passed entity
        /// and the entity retrieved from DB upon the query defined, ONLY ValueTypes and strings are
        /// mapped between both entities, NO nested objects will be mapped, you have to do
        /// the objects mapping nested in your entity before calling this method</remarks>
        protected virtual void Update(T entity, Expression<Func<T, bool>> query)
        {
            using (DC db = new DC())
            {
                object propertyValue = null;
                T entityFromDB = db.GetTable<T>().Where(query).SingleOrDefault();

                if (null == entityFromDB)
                    throw new NullReferenceException("Query Supplied to Get entity from DB is invalid, NULL value returned");

                PropertyInfo[] properties = entityFromDB.GetType().GetProperties();

                foreach (PropertyInfo property in properties)
                {
                    propertyValue = null;

                    if (null != property.GetSetMethod())
                    {
                        PropertyInfo entityProperty = entity.GetType().GetProperty(property.Name);

                        if (entityProperty.PropertyType.BaseType == Type.GetType("System.ValueType") || entityProperty.PropertyType == Type.GetType("System.String"))
                            propertyValue = entity.GetType().GetProperty(property.Name).GetValue(entity, null);

                        if (null != propertyValue)
                            property.SetValue(entityFromDB, propertyValue, null);
                    }
                }

                db.SubmitChanges();
            }
        }
    }
}
